#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <fcntl.h>
#include <unistd.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <pthread.h>

#include <sys/epoll.h>

#include "include_all.h"

static int setnonblocking(int *);
static int recvhandler(int);
static void *thread_listen_epoll(void *);

static struct watchdog_info *wtd = NULL;
static int clent_nums = 0;

int listen_init(struct watchdog_info *watchdog, int port)
{
	/* pthread */
	pthread_t tid = (pthread_t)0;

	/* socket */
	const int opt = 1;
	int *sock = NULL;
	struct sockaddr_in local_addr;

	/* watchdog */
	wtd = watchdog;

	/* malloc socket */
	sock = (int *)malloc(sizeof(int));

	/* create socket */
	*sock = socket(AF_INET, SOCK_STREAM, 0);
	if(*sock < 0)
	{
		return RET_FAIL;
	}

	/* set local address */
	memset(&local_addr, 0, sizeof(struct sockaddr_in));
	local_addr.sin_family = PF_INET;
	local_addr.sin_port = htons(port);
	local_addr.sin_addr.s_addr = htonl(INADDR_ANY);

	/* port reuse */
	if(setsockopt(*sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0)
	{
		return RET_FAIL;
	}

	/* bind */
	if(bind(*sock, (struct sockaddr *)&local_addr, sizeof(struct sockaddr_in)) < 0)
	{
		return RET_FAIL;
	}

	/* start listen */
	if(listen(*sock, 5) < 0)
	{
		return RET_FAIL;
	}

	/* set socket noblock */
	if(setnonblocking(sock) != RET_OK)
	{
		return RET_FAIL;
	}

	if(pthread_create(&tid, NULL, thread_listen_epoll, sock) == 0)
	{
		return RET_OK;
	}

	return RET_FAIL;
}

static int setnonblocking(int *sock)
{
	int opts;
	opts = fcntl(*sock, F_GETFL);
	if(opts < 0)
	{
		return RET_FAIL;
	}
	if(fcntl(*sock, F_SETFL, opts | O_NONBLOCK)<0)
	{
		return RET_FAIL;
	}
	return RET_OK;
}

static int recvhandler(int sock)
{
	char buf[READBUF_SIZE];
	int read_size = 0;

	if(sock == STDIN_FILENO || sock == STDOUT_FILENO || sock == STDERR_FILENO)
	{
		return sock;
	}

	memset(buf, 0, READBUF_SIZE);

	read_size = read(sock, buf, READBUF_SIZE);
	if(read_size > 0)
	{
		printf("%s: %s\n", __func__, buf);
		return 1;
	}
	else if(read_size == 0)
	{
		return 0;
	}
	else
	{
		return -1;
	}
}

static void *thread_listen_epoll(void *arg)
{
	/* general */
	int i = 0;
	int ret = 0;

	/* socket */
	int *sock = (int *)arg;

	/* client socket */
	struct sockaddr_in client_addr;
	socklen_t client_len = (socklen_t)(sizeof(struct sockaddr_in));

	/* epoll */
	int epoll_fd = 0, epoll_events_count = 0;
	struct epoll_event ev, events[EPOLL_SIZE];

	ev.events = EPOLLIN | EPOLLET;
	ev.data.fd = *sock;

	epoll_fd = epoll_create(EPOLL_SIZE);
	if(epoll_fd < 0)
	{
		wtd->report_reg(__func__, "epoll_fd create failed", NULL);
	}

	if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, *sock, &ev) < 0)
	{
		wtd->report_reg(__func__, "epoll_ctl failed", NULL);
	}

	wtd->run_reg(RT_TCP);
	while(wtd->run_get(0) > 0)
	{
#if DEBUG
		printf("%d\n", clent_nums);
#endif
		epoll_events_count = epoll_wait(epoll_fd, events, EPOLL_SIZE, EPOLL_RUN_TIMEOUT);
		if(epoll_events_count > 0)
		{
			/* normal, read */
			for(i = 0; i < epoll_events_count; i++)
			{
				/* new client */
				if(events[i].data.fd == (*sock))
				{
					ev.data.fd = accept(*sock, (struct sockaddr *)&client_addr, &client_len);
					if(setnonblocking(&(ev.data.fd)) != RET_OK)
					{
						wtd->report_reg(__func__, "client setnoblock failed", NULL);
					}
					if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, ev.data.fd, &ev) < 0)
					{
						wtd->report_reg(__func__, "epoll_ctl failed", NULL);
					}
					printf("client connect, fd = %d\n", ev.data.fd);
					clent_nums++;
				}
				else
				{
					/* other client, send message to me */
					ret = recvhandler(events[i].data.fd);

					/* client was disconnect */
					if(ret <= 0)
					{
						close(events[i].data.fd);
						printf("client disconnect, fd = %d\n", events[i].data.fd);
						clent_nums--;
					}
				}
			}
		}
		else if(epoll_events_count < 0)
		{
			/* error, report to main */
			wtd->report_reg(__func__, "epoll_wait failed", NULL);
		}
		else
		{
			/*time out*/
			continue;
		}
	}
	free(sock);

	for(i = 0; i < EPOLL_SIZE; i++)
	{
		if(i == STDIN_FILENO || i == STDOUT_FILENO || i == STDERR_FILENO)
		{
			continue;
		}
		close(i);
	}

#if DEBUG
	printf("%s: listen stop, I will exit!\n", __func__);
#endif

	wtd->run_unreg(RT_TCP);
	return NULL;
}
